﻿using System;
using System.Configuration;
using System.ServiceModel.Description;
using System.IO;
using System.Xml.Linq;
using System.Security;
using System.Runtime.InteropServices;
using System.DirectoryServices.AccountManagement;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Sdk.Discovery;
using Microsoft.Xrm.Sdk;
using Microsoft.Crm.Services.Utility;

namespace QueueManager.Helper.Crm.Old
{
  /// <summary>
  /// Provides server connection information.
  /// </summary>
  public class ServerConnection
  {
    #region Inner classes
    /// <summary>
    /// Stores Microsoft Dynamics CRM server configuration information.
    /// </summary>
    public class Configuration
    {
      public String ServerAddress;
      public String OrganizationName;
      public Uri DiscoveryUri;
      public Uri OrganizationUri;
      public Uri HomeRealmUri;
      public ClientCredentials DeviceCredentials;
      public ClientCredentials Credentials;
      public AuthenticationProviderType EndpointType;
      public String UserPrincipalName;
      #region internal members of the class
      internal IServiceManagement<IOrganizationService> OrganizationServiceManagement;
      internal SecurityTokenResponse OrganizationTokenResponse;
      internal Int16 AuthFailureCount;
      #endregion

      public override bool Equals(object obj)
      {
        //Check for null and compare run-time types.
        if (obj == null || GetType() != obj.GetType()) return false;

        var c = (Configuration)obj;

        if (!ServerAddress.Equals(c.ServerAddress, StringComparison.InvariantCultureIgnoreCase))
          return false;
        if (!OrganizationName.Equals(c.OrganizationName, StringComparison.InvariantCultureIgnoreCase))
          return false;
        if (EndpointType != c.EndpointType)
          return false;
        if (EndpointType == AuthenticationProviderType.ActiveDirectory)
        {
          if (!Credentials.Windows.ClientCredential.Domain.Equals(
              c.Credentials.Windows.ClientCredential.Domain, StringComparison.InvariantCultureIgnoreCase))
            return false;
          if (!Credentials.Windows.ClientCredential.UserName.Equals(
              c.Credentials.Windows.ClientCredential.UserName, StringComparison.InvariantCultureIgnoreCase))
            return false;
        }
        else if (EndpointType == AuthenticationProviderType.LiveId)
        {
          if (!Credentials.UserName.UserName.Equals(c.Credentials.UserName.UserName,
              StringComparison.InvariantCultureIgnoreCase))
            return false;
          if (!DeviceCredentials.UserName.UserName.Equals(
              c.DeviceCredentials.UserName.UserName, StringComparison.InvariantCultureIgnoreCase))
            return false;
          if (!DeviceCredentials.UserName.Password.Equals(
              c.DeviceCredentials.UserName.Password, StringComparison.InvariantCultureIgnoreCase))
            return false;
        }
        else
        {
          if (!Credentials.UserName.UserName.Equals(c.Credentials.UserName.UserName,
              StringComparison.InvariantCultureIgnoreCase))
            return false;
        }
        return true;
      }

      public override int GetHashCode()
      {
        int returnHashCode = ServerAddress.GetHashCode()
            ^ OrganizationName.GetHashCode()
            ^ EndpointType.GetHashCode();

        if (EndpointType == AuthenticationProviderType.ActiveDirectory)
          returnHashCode = returnHashCode
              ^ Credentials.Windows.ClientCredential.UserName.GetHashCode()
              ^ Credentials.Windows.ClientCredential.Domain.GetHashCode();
        else if (EndpointType == AuthenticationProviderType.LiveId)
          returnHashCode = returnHashCode
              ^ Credentials.UserName.UserName.GetHashCode()
              ^ DeviceCredentials.UserName.UserName.GetHashCode()
              ^ DeviceCredentials.UserName.Password.GetHashCode();
        else
          returnHashCode = returnHashCode
              ^ Credentials.UserName.UserName.GetHashCode();

        return returnHashCode;
      }

    }
    #endregion Inner classes

    #region Public properties

    #endregion Public properties

    #region Private properties

    public Configuration config = new Configuration();

    #endregion Private properties

    #region Static methods
    /// <summary>
    /// Obtains the organization service proxy.
    /// </summary>
    /// <param name="serverConfiguration"></param>
    /// <returns></returns>
    public static OrganizationServiceProxy GetOrganizationProxy(Configuration serverConfiguration)
    {
      // Obtain the organization service proxy for the Federated, LiveId, and OnlineFederated environments. 
      if (serverConfiguration.OrganizationServiceManagement != null
          && serverConfiguration.OrganizationTokenResponse != null)
      {
        return new OrganizationServiceProxy(
            serverConfiguration.OrganizationServiceManagement,
            serverConfiguration.OrganizationTokenResponse);
      }

      if (serverConfiguration.OrganizationServiceManagement == null)
        throw new ArgumentNullException("serverConfiguration");

      // Obtain the organization service proxy for the ActiveDirectory environment.
      return new OrganizationServiceProxy(
          serverConfiguration.OrganizationServiceManagement,
          serverConfiguration.Credentials);

    }
    #endregion Static methods

    #region Public methods
    /// <summary>
    /// Obtains the server connection information including the target organization's
    /// Uri and user logon credentials from the user.
    /// </summary>
    public virtual Configuration GetServerConfiguration()
    {
      // Read the configuration from the disk, if it exists, at C:\Users\<username>\AppData\Roaming\CrmServer\Credentials.xml.
      var isConfigExist = ReadConfigurations();

      // Check if server configuration settings are already available on the disk.
      // Get the existing user's logon credentials.
      config.Credentials = GetStoredUserLogonCredentials();

      // Set IServiceManagement for the current organization.
      var orgServiceManagement = ServiceConfigurationFactory.CreateManagement<IOrganizationService>(config.OrganizationUri);
      config.OrganizationServiceManagement = orgServiceManagement;

      // Set SecurityTokenResponse for the current organization.
      if (config.EndpointType != AuthenticationProviderType.ActiveDirectory)
      {
        // Set the credentials.
        var authCredentials = new AuthenticationCredentials();

        // If UserPrincipalName exists, use it. Otherwise, set the logon credentials from the configuration.
        if (!String.IsNullOrWhiteSpace(config.UserPrincipalName))
        {
          authCredentials.UserPrincipalName = config.UserPrincipalName;
        }
        else
        {
          authCredentials.ClientCredentials = config.Credentials;
          if (config.EndpointType == AuthenticationProviderType.LiveId)
          {
            authCredentials.SupportingCredentials = new AuthenticationCredentials();
            authCredentials.SupportingCredentials.ClientCredentials = config.DeviceCredentials;
          }
        }
        var tokenCredentials = orgServiceManagement.Authenticate(authCredentials);
        if (tokenCredentials != null)
        {
          if (tokenCredentials.SecurityTokenResponse != null)
            config.OrganizationTokenResponse = tokenCredentials.SecurityTokenResponse;
        }
      }

      return config;
    }

    public virtual Configuration SetServerConfiguration(string serverAddress, bool useSecureSocketLayer, bool office365)
    {
      if (serverAddress.EndsWith(".dynamics.com") || String.IsNullOrWhiteSpace(serverAddress))
        useSecureSocketLayer = true;

      config.ServerAddress = serverAddress;

      // One of the Microsoft Dynamics CRM Online data centers.
      if (config.ServerAddress.EndsWith(".dynamics.com", StringComparison.InvariantCultureIgnoreCase))
      {
        // Check if the organization is provisioned in Microsoft Office 365.
        if (office365)
        {
          config.DiscoveryUri = new Uri(String.Format("https://disco.{0}/XRMServices/2011/Discovery.svc", config.ServerAddress));
        }
        else
        {
          config.DiscoveryUri = new Uri(String.Format("https://dev.{0}/XRMServices/2011/Discovery.svc", config.ServerAddress));

          // Get or set the device credentials. This is required for Windows Live ID authentication. 
          config.DeviceCredentials = DeviceIdManager.LoadOrRegisterDevice();
        }
      }
      // Check if the server uses Secure Socket Layer (https).
      else if (useSecureSocketLayer)
        config.DiscoveryUri = new Uri(String.Format("https://{0}/XRMServices/2011/Discovery.svc", config.ServerAddress));
      else
        config.DiscoveryUri = new Uri(String.Format("http://{0}/XRMServices/2011/Discovery.svc", config.ServerAddress));

      // Get the target organization.
      //config.OrganizationUri = organizationUri;

      //Set endpoint type
      config.EndpointType = GetEndpointType(config.DiscoveryUri);

      return config;
    }
    public OrganizationDetailCollection GetOrganizations(ClientCredentials clientCredentials)
    {
      using (var serviceProxy = GetDiscoveryProxy(clientCredentials))
      {
        // Obtain organization information from the Discovery service. 
        if (serviceProxy != null)
        {
          // Obtain information about the organizations that the system user belongs to.
          return DiscoverOrganizations(serviceProxy);
        }
        else
          throw new Exception("An invalid server name was specified.");
      }
    }

    public virtual Configuration SetServerConfiguration2(string serverAddress, bool useSecureSocketLayer, bool office365, Uri organizationUri)
    {
      // Set IServiceManagement for the current organization.
      var orgServiceManagement = ServiceConfigurationFactory.CreateManagement<IOrganizationService>(config.OrganizationUri);
      config.OrganizationServiceManagement = orgServiceManagement;

      //Set authentication

      // Set SecurityTokenResponse for the current organization.
      if (config.EndpointType != AuthenticationProviderType.ActiveDirectory)
      {
        // Set the credentials.
        var authCredentials = new AuthenticationCredentials();

        // If UserPrincipalName exists, use it. Otherwise, set the logon credentials from the configuration.
        if (!String.IsNullOrWhiteSpace(config.UserPrincipalName))
        {
          authCredentials.UserPrincipalName = config.UserPrincipalName;
        }
        else
        {
          authCredentials.ClientCredentials = config.Credentials;
          if (config.EndpointType == AuthenticationProviderType.LiveId)
          {
            authCredentials.SupportingCredentials = new AuthenticationCredentials();
            authCredentials.SupportingCredentials.ClientCredentials = config.DeviceCredentials;
          }
        }
        var tokenCredentials = orgServiceManagement.Authenticate(authCredentials);
        if (tokenCredentials != null)
        {
          if (tokenCredentials.SecurityTokenResponse != null)
            config.OrganizationTokenResponse = tokenCredentials.SecurityTokenResponse;
        }
      }

      return config;
    }

    private AuthenticationProviderType GetEndpointType(Uri discoveryUri)
    {
      var serviceManagement = ServiceConfigurationFactory.CreateManagement<IDiscoveryService>(discoveryUri);
      return serviceManagement.AuthenticationType;
    }


    /// <summary>
    /// Discovers the organizations that the calling user belongs to.
    /// </summary>
    /// <param name="service">A Discovery service proxy instance.</param>
    /// <returns>Array containing detailed information on each organization that 
    /// the user belongs to.</returns>
    private OrganizationDetailCollection DiscoverOrganizations(IDiscoveryService service)
    {
      if (service == null) throw new ArgumentNullException("service");
      var orgRequest = new RetrieveOrganizationsRequest();
      var orgResponse = (RetrieveOrganizationsResponse)service.Execute(orgRequest);

      return orgResponse.Details;
    }

    /// <summary>
    /// Finds a specific organization detail in the array of organization details
    /// returned from the Discovery service.
    /// </summary>
    /// <param name="orgFriendlyName">The friendly name of the organization to find.</param>
    /// <param name="orgDetails">Array of organization detail object returned from the discovery service.</param>
    /// <returns>Organization details or null if the organization was not found.</returns>
    public OrganizationDetail FindOrganization(string orgFriendlyName, OrganizationDetail[] orgDetails)
    {
      if (String.IsNullOrWhiteSpace(orgFriendlyName))
        throw new ArgumentNullException("orgFriendlyName");
      if (orgDetails == null)
        throw new ArgumentNullException("orgDetails");
      OrganizationDetail orgDetail = null;

      foreach (OrganizationDetail detail in orgDetails)
      {
        if (String.Compare(detail.FriendlyName, orgFriendlyName,
            StringComparison.InvariantCultureIgnoreCase) == 0)
        {
          orgDetail = detail;
          break;
        }
      }
      return orgDetail;
    }

    /// <summary>
    /// Reads a server configuration file.
    /// Read the configuration from disk, if it exists, at C:\Users\YourUserName\AppData\Roaming\CrmServer\Credentials.xml.
    /// </summary>
    /// <returns>Is configuration settings already available on disk.</returns>
    public Boolean ReadConfigurations()
    {
      var isConfigExist = false;

      //Read config from app.config instead
      config.ServerAddress = ConfigurationManager.AppSettings["ServerAddress"];
      config.OrganizationName = ConfigurationManager.AppSettings["OrganizationName"];
      if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings["DiscoveryUri"]))
        config.DiscoveryUri = new Uri(ConfigurationManager.AppSettings["DiscoveryUri"]);
      if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings["OrganizationUri"]))
        config.OrganizationUri = new Uri(ConfigurationManager.AppSettings["OrganizationUri"]);
      if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings["HomeRealmUri"]))
        config.HomeRealmUri = new Uri(ConfigurationManager.AppSettings["HomeRealmUri"]);
      config.EndpointType = RetrieveAuthenticationType(ConfigurationManager.AppSettings["EndpointType"]);
      config.Credentials = ParseInCredentials(ConfigurationManager.AppSettings["Username"], ConfigurationManager.AppSettings["Domain"], config.EndpointType);
      if (config.EndpointType == AuthenticationProviderType.LiveId)
        config.DeviceCredentials = GetDeviceCredentials();
      config.UserPrincipalName = ConfigurationManager.AppSettings["UserPrincipalName"];

      ////End
      //if (File.Exists(CrmServiceHelperConstants.ServerCredentialsFile))
      //{
      //  XElement configurationsFromFile = XElement.Load(CrmServiceHelperConstants.ServerCredentialsFile);
      //  foreach (XElement config in configurationsFromFile.Nodes())
      //  {
      //    Configuration newConfig = new Configuration();
      //    var serverAddress = config.Element("ServerAddress");
      //    if (serverAddress != null)
      //      if (!String.IsNullOrEmpty(serverAddress.Value))
      //        newConfig.ServerAddress = serverAddress.Value;
      //    var organizationName = config.Element("OrganizationName");
      //    if (organizationName != null)
      //      if (!String.IsNullOrEmpty(organizationName.Value))
      //        newConfig.OrganizationName = organizationName.Value;
      //    var discoveryUri = config.Element("DiscoveryUri");
      //    if (discoveryUri != null)
      //      if (!String.IsNullOrEmpty(discoveryUri.Value))
      //        newConfig.DiscoveryUri = new Uri(discoveryUri.Value);
      //    var organizationUri = config.Element("OrganizationUri");
      //    if (organizationUri != null)
      //      if (!String.IsNullOrEmpty(organizationUri.Value))
      //        newConfig.OrganizationUri = new Uri(organizationUri.Value);
      //    var homeRealmUri = config.Element("HomeRealmUri");
      //    if (homeRealmUri != null)
      //      if (!String.IsNullOrEmpty(homeRealmUri.Value))
      //        newConfig.HomeRealmUri = new Uri(homeRealmUri.Value);

      //    var vendpointType = config.Element("EndpointType");
      //    if (vendpointType != null)
      //      newConfig.EndpointType =
      //              RetrieveAuthenticationType(vendpointType.Value);
      //    if (config.Element("Credentials").HasElements)
      //    {
      //      newConfig.Credentials =
      //          ParseInCredentials(config.Element("Credentials"), newConfig.EndpointType);
      //    }
      //    if (newConfig.EndpointType == AuthenticationProviderType.LiveId)
      //    {
      //      newConfig.DeviceCredentials = GetDeviceCredentials();
      //    }
      //    var userPrincipalName = config.Element("UserPrincipalName");
      //    if (userPrincipalName != null)
      //      if (!String.IsNullOrWhiteSpace(userPrincipalName.Value))
      //        newConfig.UserPrincipalName = userPrincipalName.Value;
      //    configurations.Add(newConfig);
      //  }
      //}

      return true;
    }

    #endregion Public methods

    #region Protected methods


    /// <summary>
    /// Obtains the user's logon credentials for the target server.
    /// </summary>
    /// <returns>Logon credentials of the user.</returns>
    protected virtual ClientCredentials GetStoredUserLogonCredentials()
    {
      var credentials = new ClientCredentials();
      var isCredentialExist = (config.Credentials != null);
      switch (config.EndpointType)
      {
        // An on-premises Microsoft Dynamics CRM server deployment. 
        case AuthenticationProviderType.ActiveDirectory:
          var userName = ConfigurationManager.AppSettings["Username"];
          var password = ConfigurationManager.AppSettings["Password"];
          var domain = ConfigurationManager.AppSettings["Domain"];
          credentials.Windows.ClientCredential = new System.Net.NetworkCredential(userName, password, domain);
          break;
        // A Microsoft Dynamics CRM Online server deployment. 
        case AuthenticationProviderType.LiveId:
          credentials.UserName.UserName = ConfigurationManager.AppSettings["Username"]; ;
          credentials.UserName.Password = ConfigurationManager.AppSettings["Password"]; ;
          break;
        // An internet-facing deployment (IFD) of Microsoft Dynamics CRM.          
        case AuthenticationProviderType.Federation:
          credentials.UserName.UserName = ConfigurationManager.AppSettings["Username"];
          credentials.UserName.Password = ConfigurationManager.AppSettings["Password"];
          break;
        // Managed Identity/Federated Identity users using Microsoft Office 365.
        case AuthenticationProviderType.OnlineFederation:
          if (!isCredentialExist && config.AuthFailureCount == 0)
          {
            // Initial try with the current UserPrincipalName for the Federated Identity organization.
            // else config.UserPrincipalName has value, and it will use the same existing UserPrincipalName.
            if (String.IsNullOrWhiteSpace(config.UserPrincipalName))
              config.UserPrincipalName = UserPrincipal.Current.UserPrincipalName;
            // using existing UserPrincipalName instead of UserName & Password.
            return null;
          }

          // Fetch/Request user credentials. 
          config.UserPrincipalName = String.Empty;
          credentials.UserName.UserName = ConfigurationManager.AppSettings["Username"];
          credentials.UserName.Password = ConfigurationManager.AppSettings["Password"];
          break;
        default:
          credentials = null;
          break;
      }
      return credentials;
    }

    /// <summary>
    /// Obtains the user's logon credentials for the target server.
    /// </summary>
    /// <returns>Logon credentials of the user.</returns>
    public virtual ClientCredentials CreateUserLogonCredentials(string usernameInput, string password)
    {
      var credentials = new ClientCredentials();
      switch (config.EndpointType)
      {
        // An on-premises Microsoft Dynamics CRM server deployment. 
        case AuthenticationProviderType.ActiveDirectory:
          String[] domainAndUserNameArray;
          do
          {
            Console.Write("\nEnter domain\\username: ");
            domainAndUserNameArray = usernameInput.Split('\\');
            if (domainAndUserNameArray.Length == 1 && String.IsNullOrWhiteSpace(domainAndUserNameArray[0]))
              return null;
          }
          while (usernameInput.Length != 2 || String.IsNullOrWhiteSpace(domainAndUserNameArray[0]) || String.IsNullOrWhiteSpace(domainAndUserNameArray[1]));

          var domain = domainAndUserNameArray[0];
          var userName = domainAndUserNameArray[1];
          credentials.Windows.ClientCredential = String.IsNullOrEmpty(password) ? new System.Net.NetworkCredential(userName, password, domain) : null;
          break;

        // A Microsoft Dynamics CRM Online server deployment. 
        case AuthenticationProviderType.LiveId:
          if (string.IsNullOrWhiteSpace(usernameInput))
            return null;

          credentials.UserName.UserName = usernameInput;
          credentials.UserName.Password = password;
          break;

        // An internet-facing deployment (IFD) of Microsoft Dynamics CRM.          
        case AuthenticationProviderType.Federation:
          if (string.IsNullOrWhiteSpace(usernameInput))
            return null;

          credentials.UserName.UserName = usernameInput;
          credentials.UserName.Password = password;
          break;

        // Managed Identity/Federated Identity users using Microsoft Office 365.
        case AuthenticationProviderType.OnlineFederation:
          if (config.AuthFailureCount == 0)
          {
            // Initial try with the current UserPrincipalName for the Federated Identity organization.
            // else config.UserPrincipalName has value, and it will use the same existing UserPrincipalName.
            if (String.IsNullOrWhiteSpace(config.UserPrincipalName))
              config.UserPrincipalName = UserPrincipal.Current.UserPrincipalName;
            // using existing UserPrincipalName instead of UserName & Password.
            return null;
          }

          // Fetch/Request user credentials. 
          config.UserPrincipalName = String.Empty;

          // Request Managed Identity/Federated Identity username and password.
          if (string.IsNullOrWhiteSpace(usernameInput))
            return null;

          credentials.UserName.UserName = usernameInput;
          credentials.UserName.Password = password;
          break;
        default:
          credentials = null;
          break;
      }
      return credentials;
    }


    /// <summary>
    /// Get the device credentials by either loading from the local cache 
    /// or request new device credentials by registering the device.
    /// </summary>
    /// <returns>Device Credentials.</returns>
    protected virtual ClientCredentials GetDeviceCredentials()
    {
      return DeviceIdManager.LoadOrRegisterDevice();
    }

    /// <summary>
    /// Get the discovery service proxy based on existing configuration data.
    /// Added new way of getting discovery proxy.
    /// Also preserving old way of getting discovery proxy to support old scenarios.
    /// </summary>
    /// <returns>An instance of DiscoveryServiceProxy</returns>
    private DiscoveryServiceProxy GetDiscoveryProxy()
    {
      var serviceManagement = ServiceConfigurationFactory.CreateManagement<IDiscoveryService>(config.DiscoveryUri);

      // Get the EndpointType.
      config.EndpointType = serviceManagement.AuthenticationType;

      // Get the logon credentials.
      config.Credentials = GetStoredUserLogonCredentials();

      var authCredentials = new AuthenticationCredentials();

      if (!String.IsNullOrWhiteSpace(config.UserPrincipalName))
      {
        // Try to authenticate the Federated Identity organization with UserPrinicipalName.
        authCredentials.UserPrincipalName = config.UserPrincipalName;

        try
        {
          var tokenCredentials = serviceManagement.Authenticate(authCredentials);
          var discoveryProxy = new DiscoveryServiceProxy(serviceManagement, tokenCredentials.SecurityTokenResponse);

          // Checking authentication by invoking some SDK methods.
          var orgs = DiscoverOrganizations(discoveryProxy);
          return discoveryProxy;
        }
        catch (System.ServiceModel.Security.SecurityAccessDeniedException ex)
        {
          // If authentication failed using current UserPrincipalName, 
          // request UserName and Password to try to authenticate using user credentials.
          if (ex.Message.Contains("Access is denied."))
          {
            config.AuthFailureCount += 1;
            authCredentials.UserPrincipalName = String.Empty;

            config.Credentials = GetStoredUserLogonCredentials();
          }
          else
          {
            throw ex;
          }
        }
        // You can also catch other exceptions to handle a specific situation in your code, for example, 
        //      System.ServiceModel.Security.ExpiredSecurityTokenException
        //      System.ServiceModel.Security.MessageSecurityException
        //      System.ServiceModel.Security.SecurityNegotiationException                
      }

      // Resetting credentials in the AuthenicationCredentials.  
      if (config.EndpointType != AuthenticationProviderType.ActiveDirectory)
      {
        authCredentials = new AuthenticationCredentials();
        authCredentials.ClientCredentials = config.Credentials;

        if (config.EndpointType == AuthenticationProviderType.LiveId)
        {
          authCredentials.SupportingCredentials = new AuthenticationCredentials();
          authCredentials.SupportingCredentials.ClientCredentials = config.DeviceCredentials;
        }
        // Try to authenticate with the user credentials.
        var tokenCredentials1 = serviceManagement.Authenticate(authCredentials);
        return new DiscoveryServiceProxy(serviceManagement, tokenCredentials1.SecurityTokenResponse);
      }
      // For an on-premises environment.
      return new DiscoveryServiceProxy(serviceManagement, config.Credentials);
    }

    /// <summary>
    /// Get the discovery service proxy based on existing configuration data.
    /// Added new way of getting discovery proxy.
    /// Also preserving old way of getting discovery proxy to support old scenarios.
    /// </summary>
    /// <returns>An instance of DiscoveryServiceProxy</returns>
    private DiscoveryServiceProxy GetDiscoveryProxy(ClientCredentials clientCredentials)
    {
      var serviceManagement = ServiceConfigurationFactory.CreateManagement<IDiscoveryService>(config.DiscoveryUri);

      // Get the EndpointType.
      config.EndpointType = serviceManagement.AuthenticationType;

      // Get the logon credentials.
      config.Credentials = clientCredentials;

      var authCredentials = new AuthenticationCredentials();

      if (!String.IsNullOrWhiteSpace(config.UserPrincipalName))
      {
        // Try to authenticate the Federated Identity organization with UserPrinicipalName.
        authCredentials.UserPrincipalName = config.UserPrincipalName;

        try
        {
          var tokenCredentials = serviceManagement.Authenticate(authCredentials);
          var discoveryProxy = new DiscoveryServiceProxy(serviceManagement, tokenCredentials.SecurityTokenResponse);

          // Checking authentication by invoking some SDK methods.
          var orgs = DiscoverOrganizations(discoveryProxy);
          return discoveryProxy;
        }
        catch (System.ServiceModel.Security.SecurityAccessDeniedException ex)
        {
          // If authentication failed using current UserPrincipalName, 
          // request UserName and Password to try to authenticate using user credentials.
          if (ex.Message.Contains("Access is denied."))
          {
            config.AuthFailureCount += 1;
            authCredentials.UserPrincipalName = String.Empty;

            config.Credentials = GetStoredUserLogonCredentials();
          }
          else
          {
            throw ex;
          }
        }
        // You can also catch other exceptions to handle a specific situation in your code, for example, 
        //      System.ServiceModel.Security.ExpiredSecurityTokenException
        //      System.ServiceModel.Security.MessageSecurityException
        //      System.ServiceModel.Security.SecurityNegotiationException                
      }

      // Resetting credentials in the AuthenicationCredentials.  
      if (config.EndpointType != AuthenticationProviderType.ActiveDirectory)
      {
        authCredentials = new AuthenticationCredentials();
        authCredentials.ClientCredentials = config.Credentials;

        if (config.EndpointType == AuthenticationProviderType.LiveId)
        {
          authCredentials.SupportingCredentials = new AuthenticationCredentials();
          authCredentials.SupportingCredentials.ClientCredentials = config.DeviceCredentials;
        }
        // Try to authenticate with the user credentials.
        var tokenCredentials1 = serviceManagement.Authenticate(authCredentials);
        return new DiscoveryServiceProxy(serviceManagement, tokenCredentials1.SecurityTokenResponse);
      }
      // For an on-premises environment.
      return new DiscoveryServiceProxy(serviceManagement, config.Credentials);
    }

    /// <summary>
    /// Verify passed strings with the supported AuthenticationProviderType.
    /// </summary>
    /// <param name="authType">String AuthenticationType</param>
    /// <returns>Supported AuthenticatoinProviderType</returns>
    private AuthenticationProviderType RetrieveAuthenticationType(String authType)
    {
      switch (authType)
      {
        case "ActiveDirectory":
          return AuthenticationProviderType.ActiveDirectory;
        case "LiveId":
          return AuthenticationProviderType.LiveId;
        case "Federation":
          return AuthenticationProviderType.Federation;
        case "OnlineFederation":
          return AuthenticationProviderType.OnlineFederation;
        default:
          throw new ArgumentException(String.Format("{0} is not a valid authentication type", authType));
      }
    }

    /// <summary>
    /// Parse credentials from an XML node to required ClientCredentials data type 
    /// based on passed AuthenticationProviderType.
    /// </summary>
    /// <param name="credentials">Credential XML node.</param>
    /// <param name="endpointType">AuthenticationProviderType of the credential.</param>
    /// <returns>Required ClientCredentials type.</returns>
    private ClientCredentials ParseInCredentials(string userName, string domain, AuthenticationProviderType endpointType)
    {
      ClientCredentials result = new ClientCredentials();

      switch (endpointType)
      {
        case AuthenticationProviderType.ActiveDirectory:
          result.Windows.ClientCredential = new System.Net.NetworkCredential()
          {
            UserName = userName,
            Domain = domain
          };
          break;
        case AuthenticationProviderType.LiveId:
        case AuthenticationProviderType.Federation:
        case AuthenticationProviderType.OnlineFederation:
          result.UserName.UserName = userName;
          break;
        default:
          break;
      }

      return result;
    }

    /// <summary>
    /// Parse ClientCredentials into XML node. 
    /// </summary>
    /// <param name="clientCredentials">ClientCredentials type.</param>
    /// <param name="endpointType">AuthenticationProviderType of the credentials.</param>
    /// <returns>XML node containing credentials data.</returns>
    private XElement ParseOutCredentials(ClientCredentials clientCredentials,
        AuthenticationProviderType endpointType)
    {
      if (clientCredentials != null)
      {
        switch (endpointType)
        {
          case AuthenticationProviderType.ActiveDirectory:
            return new XElement("Credentials",
                new XElement("UserName", clientCredentials.Windows.ClientCredential.UserName),
                new XElement("Domain", clientCredentials.Windows.ClientCredential.Domain)
                );
          case AuthenticationProviderType.LiveId:
          case AuthenticationProviderType.Federation:
          case AuthenticationProviderType.OnlineFederation:
            return new XElement("Credentials",
               new XElement("UserName", clientCredentials.UserName.UserName)
               );
          default:
            break;
        }
      }

      return new XElement("Credentials", "");
    }

    /// <summary>
    /// Convert SecureString to unsecure string.
    /// </summary>
    /// <param name="securePassword">Pass SecureString for conversion.</param>
    /// <returns>unsecure string</returns>
    private string ConvertToUnsecureString(SecureString securePassword)
    {
      if (securePassword == null)
        throw new ArgumentNullException("securePassword");

      IntPtr unmanagedString = IntPtr.Zero;
      try
      {
        unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(securePassword);
        return Marshal.PtrToStringUni(unmanagedString);
      }
      finally
      {
        Marshal.ZeroFreeGlobalAllocUnicode(unmanagedString);
      }
    }

    /// <summary>
    /// Convert unsecure string to SecureString.
    /// </summary>
    /// <param name="password">Pass unsecure string for conversion.</param>
    /// <returns>SecureString</returns>
    private SecureString ConvertToSecureString(string password)
    {
      if (password == null)
        throw new ArgumentNullException("password");

      var securePassword = new SecureString();
      foreach (char c in password)
        securePassword.AppendChar(c);
      securePassword.MakeReadOnly();
      return securePassword;
    }
    #endregion Private methods

    #region Private Classes
    /// <summary>
    /// private static class to store constants required by the CrmServiceHelper class.
    /// </summary>
    private static class CrmServiceHelperConstants
    {
      /// <summary>
      /// Credentials file path.
      /// </summary>
      public static readonly string ServerCredentialsFile = Path.Combine(
          Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "CrmServer"), "Credentials.xml");
    }
    #endregion
  }
}
